/* eslint-disable global-require */
/* eslint-disable no-param-reassign */
import path from 'path';

import { getOptions } from 'loader-utils';
import validateOptions from 'schema-utils';
import colors from 'colors';
import FormData from 'form-data';
import mime from 'mime-types';

import schema from './options.json';

const UPLOADED_FILES = {};

function getMimetype(resourcePath) {
  const resolvedMimeType = mime.contentType(path.extname(resourcePath));

  if (!resolvedMimeType) {
    return '';
  }

  return resolvedMimeType.replace(/;\s+charset/i, ';charset');
}

function uploadFile(url, opt, data, content, filename, callback) {
  if (typeof content === 'string') {
    content = Buffer.from(content, 'utf8');
  } else if (!(content instanceof Buffer)) {
    process.stderr.write(`Unable to upload content [${typeof content}]`)
  }

  opt = opt || {}
  data = data || {}

  const form = new FormData();
  for (const key in data) {
    if (Object.prototype.hasOwnProperty.call(data, key)) {
      form.append(key, data[key]);
    }
  }

  form.append(opt.field || 'file', content, filename);
  form.submit(url, (err, res) => {
    if (err) {
      callback(err);
    } else {
      const status = res.statusCode;
      let body = '';

      res
        .on('data', (chunk) => {
          body += chunk;
        })
        .on('end', () => {
          if (status >= 200 && status < 300 || status === 304) {
            const jsonData = JSON.parse(body);
            if (jsonData.errcode === 0) {
              callback(null, jsonData.url);
            } else {
              const err2 = data.errmsg || 'Unknown error.';
              callback(err2);
            }
          } else {
            callback(status);
          }
        })
        .on('error', (resErr) => {
          callback(resErr.message || resErr);
        });
    }
  });
}

export default function loader(content, map, meta) {
  const callback = this.async();
  const options = getOptions(this) || {};
  const { resourcePath, rootContext } = this;
  const esModule = typeof options.esModule !== 'undefined' ? options.esModule : true;

  validateOptions(schema, options, {
    name: 'Assets Upload Loader',
    baseDataPath: 'options',
  });

  let handle = 'upload';
  if (options.rules) {
    for (const rule of options.rules) {
      const ruleTest = (typeof rule.test === 'string') ? new RegExp(`${rule.test}`) : rule.test;
      if (ruleTest.test(resourcePath)) {
        // eslint-disable-next-line prefer-destructuring
        handle = rule.handle;
        break;
      }
    }
  }

  if (options.upload && handle === 'upload') {
    const filename = path.basename(resourcePath);
    const relPath = colors.green(resourcePath.replace(rootContext, ''));
    const uploadCallback = (err, remoteUrl) => {
      if (err) {
        process.stdout.write(`Upload file ${relPath} fail. ${colors.yellow(err)}\n`);
        callback(err);
      } else {
        UPLOADED_FILES[relPath] = remoteUrl;
        process.stdout.write(`Upload file ${relPath} to ${colors.green(remoteUrl)}\n`);
        callback(null, `${
          esModule ? 'export default' : 'module.exports ='
        } ${JSON.stringify(remoteUrl)}`);
      }
    };

    if (typeof options.upload === 'function') {
      options.upload.call(this, content, filename, uploadCallback);
    } else if (UPLOADED_FILES[relPath]) {
      callback(null, `${
        esModule ? 'export default' : 'module.exports ='
      } ${JSON.stringify(UPLOADED_FILES[relPath])}`);
    } else {
      let url = '/';
      let field = 'file';
      let postData = {};
      if (typeof options.upload === 'string') {
        url = options.upload;
      } else if (typeof options.upload === 'object') {
        url = options.upload.url || url;
        field = options.upload.field || field;
        postData = options.upload.data || postData;
      }

      uploadFile(url, { field }, postData, content, filename, uploadCallback);
    }
  } else if (handle === 'base64') {
    const mimetype = getMimetype(resourcePath);

    if (typeof content === 'string') {
      // eslint-disable-next-line no-param-reassign
      content = Buffer.from(content);
    }
    const encodedData = `data:${mimetype};base64,${content.toString('base64')}`;
    callback(null, `${
      esModule ? 'export default' : 'module.exports ='
    } ${JSON.stringify(encodedData)}`);
  } else {
    const fallback = require('file-loader');
    const fallbackLoaderContext = { ...this };
    callback(null, fallback.call(fallbackLoaderContext, content, map, meta));
  }
}

export const raw = true;
